using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace _2DGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public abstract class CollisionObject : DrawObject
    {
        Color[] textureData;

        public Color[] TextureData
        {
            get { return textureData; }
        }

        public virtual Rectangle GetVisibleTextureRectangle()
        {
            return new Rectangle(0, 0, Texture.Width, Texture.Height);
        }

        class TouchingObject
        {
            public CollisionObject collisionObject;
            public bool touching;

            public TouchingObject(CollisionObject fromObject)
            {
                collisionObject = fromObject;
                touching = true;
            }
        }

        List<TouchingObject> ignoreCollisionsUntilSeparated = new List<TouchingObject>();

        public CollisionObject(String fromFilename, SpriteBlendMode fromBlendMode) :
            base(fromFilename, fromBlendMode)
        {
            if (Texture != null)
            {
                textureData = new Color[Texture.Width * Texture.Height];
                Texture.GetData<Color>(TextureData);
            }
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        public override void LoadContent()
        {
            base.LoadContent();
            textureData = new Color[Texture.Width * Texture.Height];
            Texture.GetData<Color>(textureData);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public virtual void Update(GameTime gameTime)
        {
            List<TouchingObject> removeList = new List<TouchingObject>();
            foreach (TouchingObject currentObject in ignoreCollisionsUntilSeparated)
            {
                // If objects are not touching or no longer exist remove them from list
                if (currentObject.touching == false || currentObject.collisionObject.InWorld == false)
                {
                    removeList.Add(currentObject);
                }
                else
                // Set touching to false. If they are touching it will be set to true again during update
                {
                    currentObject.touching = false;
                }
            }
            foreach (TouchingObject currentObject in removeList)
            {
                ignoreCollisionsUntilSeparated.Remove(currentObject);
            }
        }

        public void IgnoreUntilSeparated(CollisionObject newObject)
        {
            ignoreCollisionsUntilSeparated.Add(new TouchingObject(newObject));
        }

        public static bool IsColliding(Matrix transformA, CollisionObject objectA, Matrix transformB, CollisionObject objectB)
        {
            if (objectA == objectB)
                return false;

            Rectangle objectATextureRectangle = objectA.GetVisibleTextureRectangle();
            Rectangle objectBTextureRectangle = objectB.GetVisibleTextureRectangle();

            bool objectsAreTouching = false;

            // Get the bounding rectangle of the person
            Rectangle rectangleA = CalculateBoundingRectangle(
                         new Rectangle(0, 0, objectATextureRectangle.Width, objectATextureRectangle.Height),
                         transformA);

            Rectangle rectangleB = CalculateBoundingRectangle(
                         new Rectangle(0, 0, objectBTextureRectangle.Width, objectBTextureRectangle.Height),
                         transformB);

            if (rectangleA.Intersects(rectangleB))
            {
                // We always use the smaller object as 'objectA' as that's the one we iterate across
                if (objectA.Texture.Width * objectA.Texture.Height < objectB.Texture.Width * objectB.Texture.Height)
                {
                    objectsAreTouching = IntersectPixels(
                        transformA, objectATextureRectangle, objectA.Texture.Width, objectA.TextureData,
                        transformB, objectBTextureRectangle, objectB.Texture.Width, objectB.TextureData);
                }
                else
                {
                    objectsAreTouching =IntersectPixels(
                       transformB, objectBTextureRectangle, objectB.Texture.Width, objectB.TextureData,
                       transformA, objectATextureRectangle, objectA.Texture.Width, objectA.TextureData);
                }
            }

            // Check if objects are ignoring collision with each other
            // We need to do the collision check, because they're set to stop ignoring each
            // other when they are not long intersecting
            if (objectsAreTouching)
            {
                foreach (TouchingObject currentObject in objectA.ignoreCollisionsUntilSeparated)
                {
                    if (currentObject.collisionObject == objectB)
                    {
                        currentObject.touching = true;
                        return false;
                    }
                }

                foreach (TouchingObject currentObject in objectB.ignoreCollisionsUntilSeparated)
                {
                    if (currentObject.collisionObject == objectA)
                    {
                        currentObject.touching = true;
                        return false;
                    }
                }
            }

            return objectsAreTouching;
        }

        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels between two
        /// sprites.
        /// </summary>
        /// <param name="transformA">World transform of the first sprite.</param>
        /// <param name="widthA">Width of the first sprite's Texture.</param>
        /// <param name="heightA">Height of the first sprite's Texture.</param>
        /// <param name="dataA">Pixel color data of the first sprite.</param>
        /// <param name="transformB">World transform of the second sprite.</param>
        /// <param name="widthB">Width of the second sprite's Texture.</param>
        /// <param name="heightB">Height of the second sprite's Texture.</param>
        /// <param name="dataB">Pixel color data of the second sprite.</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        public static bool IntersectPixels(
                            Matrix transformA, Rectangle rectangleA, int strideA, Color[] dataA,
                            Matrix transformB, Rectangle rectangleB, int strideB, Color[] dataB)
        {
            // Calculate a matrix which transforms from A's local space into
            // world space and then into B's local space
            Matrix transformAToB = transformA * Matrix.Invert(transformB);

            // When a point moves in A's local space, it moves in B's local space with a
            // fixed direction and distance proportional to the movement in A.
            // This algorithm steps through A one pixel at a time along A's X and Y axes
            // Calculate the analogous steps in B:
            Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
            Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);

            // Calculate the top left corner of A in B's local space
            // This variable will be reused to keep track of the start of each row
            Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);

            // For each row of pixels in A
            for (int yA = 0; yA < rectangleA.Height; yA++)
            {
                // Start at the beginning of the row
                Vector2 posInB = yPosInB;

                // For each pixel in this row
                for (int xA = 0; xA < rectangleA.Width; xA++)
                {
                    // Round to the nearest pixel
                    int xB = (int)Math.Round(posInB.X);
                    int yB = (int)Math.Round(posInB.Y);

                    // If the pixel lies within the bounds of B
                    if (0 <= xB && xB < rectangleB.Width &&
                        0 <= yB && yB < rectangleB.Height)
                    {
                        // Get the colors of the overlapping pixels
                        Color colorA = dataA[(rectangleA.X + xA) + (rectangleA.Y + yA) * strideA];
                        Color colorB = dataB[(rectangleB.X + xB) + (rectangleB.Y + yB) * strideB];

                        // If both pixels are not completely transparent,
                        if (colorA.A != 0 && colorB.A != 0)
                        {
                            // then an intersection has been found
                            return true;
                        }
                    }

                    // Move to the next pixel in the row
                    posInB += stepX;
                }

                // Move to the next row
                yPosInB += stepY;
            }

            // No intersection found
            return false;
        }

        /// <summary>
        /// Calculates an axis aligned rectangle which fully contains an arbitrarily
        /// transformed axis aligned rectangle.
        /// </summary>
        /// <param name="rectangle">Original bounding rectangle.</param>
        /// <param name="transform">World transform of the rectangle.</param>
        /// <returns>A new rectangle which contains the trasnformed rectangle.</returns>
        public static Rectangle CalculateBoundingRectangle(Rectangle rectangle,
                                                           Matrix transform)
        {
            // Get all four corners in local space
            Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
            Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
            Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
            Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);

            // Transform all four corners into work space
            Vector2.Transform(ref leftTop, ref transform, out leftTop);
            Vector2.Transform(ref rightTop, ref transform, out rightTop);
            Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
            Vector2.Transform(ref rightBottom, ref transform, out rightBottom);

            // Find the minimum and maximum extents of the rectangle in world space
            Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
                                      Vector2.Min(leftBottom, rightBottom));
            Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
                                      Vector2.Max(leftBottom, rightBottom));

            // Return that as a rectangle
            return new Rectangle((int)min.X, (int)min.Y,
                                 (int)(max.X - min.X), (int)(max.Y - min.Y));
        }
    }
}